package com.example.database.demo.service.Impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.example.database.demo.constants.SFContestant;
import com.example.database.demo.mapper.PosPostOrderMapper;
import com.example.database.demo.pojo.Result;
import com.example.database.demo.pojo.SFPojo.requestData.ListFreightPush.Content;
import com.example.database.demo.pojo.SFPojo.requestData.ListFreightPush.FeeList;
import com.example.database.demo.pojo.SFPojo.requestData.ListFreightQuery.ListFreightQuery;
import com.example.database.demo.pojo.SFPojo.requestData.OrderCancel.OrderCancel;
import com.example.database.demo.pojo.SFPojo.requestData.OrderResultQuery.OrderResultQuery;
import com.example.database.demo.pojo.SFPojo.requestData.PlaceOrder.RequestParms;
import com.example.database.demo.pojo.SFPojo.requestData.PrintWayBills.PrintWayBills;
import com.example.database.demo.pojo.SFPojo.requestData.PrintWayBillsForSync.PrintWayBillsForSync;
import com.example.database.demo.pojo.SFPojo.requestData.PushOrderState.PushOrderState;
import com.example.database.demo.pojo.SFPojo.requestData.PushOrderState.orderState;
import com.example.database.demo.pojo.SFPojo.requestData.RouteQuery.RouteQuery;
import com.example.database.demo.pojo.SFPojo.responseData.PrintFile;
import com.example.database.demo.pojo.SFPojo.responseData.ResponseData;
import com.example.database.demo.service.SFPhysicalPublicService;
import com.example.database.demo.utils.CallExpressServiceTools;
import com.example.database.demo.utils.HttpClientUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author xionglin
 */
@Slf4j
@Service
public class SFPhysicalPublicServiceImpl implements SFPhysicalPublicService {
    @Value("${SF.CLIENT_CODE}")
    public String clientCode;
    @Value("${SF.CHECK_WORD}")
    public String checkWord;
    @Value("${SF.CALL_URL}")
    public String callUrl;
    @Value("${SF.PRINT_WAYBILLS}")
    public String callPrintUrl;
    @Value("${SF.PDF_DOWNLOAD_DIRECTORY}")
    public String pdfDownLoadDirectory;
    @Value("${SF.MONTHLYCARD}")
    public String MONTHLYCARD;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private RetryTemplate retryTemplate;
    @Autowired
    private PosPostOrderMapper posPostOrderMapper;

    @Override
    public List<ResponseData> placeOrder(List<RequestParms> requestParams, String serviceCode) {
        List<ResponseData> responseDataList = new CopyOnWriteArrayList<>();
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        for (RequestParms requestData : requestParams) {
            requestData.setMonthlyCard(MONTHLYCARD);
            CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
                try {
                    return retryTemplate.execute(context -> {
                        log.info("Async task running in thread: {}", Thread.currentThread().getName());
                        ResponseData responseData = doPost(requestData, serviceCode);
                        posPostOrderMapper.updateWaybillInformation(responseData.getApiResultData().getMsgData().getOrderId().split("_")[0], responseData.getApiResultData().getMsgData().getWaybillNoInfoList().get(0).getWaybillNo(), String.valueOf(requestData.getExpressTypeId()));
                        return responseData;
                    });
                } catch (Exception ex) {
                    ResponseData responseData = new ResponseData();
                    responseData.setApiResultCode("500");
                    responseData.setMsg("货运单号:" + requestData.getOrderId().split("_")[0] + "(" + ex.getMessage() + ")");
                    // 在异常情况下创建包含错误信息的ResponseData对象
                    return responseData;
                }
            }, threadPoolTaskExecutor).thenAccept(responseDataList::add);
            futures.add(future);
        }
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        return responseDataList;
    }

    @Override
    public List<ResponseData> orderResultQuery(List<OrderResultQuery> orderResultQuery, String serviceCode) throws IOException {
        return executeAsyncTasks(orderResultQuery, serviceCode);
    }


    @Override
    public List<ResponseData> orderCancel(List<OrderCancel> orderCancel, String serviceCode) {
        List<ResponseData> responseDataList = new CopyOnWriteArrayList<>();
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        for (OrderCancel requestData : orderCancel) {
            CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
                try {
                    return retryTemplate.execute(context -> {
                        log.info("Async task running in thread: {}", Thread.currentThread().getName());
                        ResponseData responseData = doPost(requestData, serviceCode);
                        posPostOrderMapper.updatepostOrderNo(responseData.getApiResultData().getMsgData().getOrderId().split("_")[0]);
                        return responseData;
                    });
                } catch (Exception ex) {
                    ResponseData responseData = new ResponseData();
                    responseData.setApiResultCode("500");
                    responseData.setMsg("货运单号:" + requestData.getOrderId().split("_")[0] + "(" + ex.getMessage() + ")");
                    // 在异常情况下创建包含错误信息的ResponseData对象
                    return responseData;
                }
            }, threadPoolTaskExecutor).thenAccept(responseDataList::add);
            futures.add(future);
        }

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        return responseDataList;

    }

    @Override
    @Retryable(maxAttempts = 10, value = RuntimeException.class)
    public ResponseData routeQuery(RouteQuery routeQuery, String serviceCode) throws Exception {
        return doPost(routeQuery, serviceCode);
    }

    @Override
    public List<ResponseData> listFreightQuery(List<ListFreightQuery> listFreightQuery, String serviceCode) throws IOException {
        return executeAsyncTasks(listFreightQuery, serviceCode);
    }

    @Override
    public Result pushListFreightQuery(String content) throws JsonProcessingException {
        Content contentEntity = objectMapper.readValue(content, Content.class);
        Result result = new Result(0, "", null, "true");
        log.info("推送报文:{}", contentEntity);
        CompletableFuture.runAsync(() -> {
            retryTemplate.execute(retryContext -> {
                List<FeeList> feeList = contentEntity.getFeeList();
                String sfFee = "";
                String sfPremium = "";
                for (FeeList list : feeList) {
                    if (ObjectUtil.equals(list.getFeeTypeCode(), "1")) {
                        sfFee = list.getFeeAmt();
                    }
                    if (ObjectUtil.equals(list.getFeeTypeCode(), "3")) {
                        sfPremium = list.getFeeAmt();
                    }
                }
                Boolean isSuccess = posPostOrderMapper.updateSfFeeInfo(contentEntity.getWaybillNo(), sfFee, sfPremium, contentEntity.getOrderNo(), contentEntity.getOrderNo().split("_")[0]);
                if (!isSuccess) {
                    throw new RuntimeException();
                }
                return isSuccess;
            });
        });
        return result;
    }

    @Override
    public List<ResponseData> printWayBills(List<PrintWayBills> printWayBills, String serviceCode) throws IOException {
        List<ResponseData> responseDataList = new CopyOnWriteArrayList<>();
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        for (PrintWayBills printWayBill : printWayBills) {
            CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
                try {
                    return retryTemplate.execute(context -> {
                        log.info("Async task running in thread: {}", Thread.currentThread().getName());
                        String doPostResult = doPostPrintWayBills(printWayBill, serviceCode);
                        ResponseData responseData = objectMapper.readValue(doPostResult, ResponseData.class);
                        if (responseData.getApiResultCode().equals(SFContestant.invalidApiResultCode)) {
                            throw new RuntimeException();
                        }
                        posPostOrderMapper.updateSFMDInfo(responseData.getApiResultData().getObj().getFiles().get(0).getWaybillNo());
                        return responseData;
                    });
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }, threadPoolTaskExecutor).thenAccept(responseDataList::add).exceptionally(ex -> {
                log.error("Max retry count reached. Discarding result for: {}", printWayBill);
                return null;
            });

            futures.add(future);
        }
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        return responseDataList;
    }

    @Override
    public ResponseData printWayBillsForSync(PrintWayBillsForSync printWayBillsForSync, String serviceCode) throws IOException {
        //TODO
        String doPostResult = doPostPrintWayBillsForSync(printWayBillsForSync, serviceCode);
        String content = JSON.parseObject(doPostResult).getString("content");
        Base64 base64Encoder = new Base64();
        byte[] fileBytes = base64Encoder.decode(content);
        Path filePath = Files.write(Paths.get("path.pdf"), fileBytes);
        // 将字节数组写入文件
        FileOutputStream outputStream = new FileOutputStream(filePath.getFileName().toString());
        outputStream.write(fileBytes);
        outputStream.close();
        String url = "/your-app-base-url/" + filePath;
        ResponseData data = new ResponseData();
        data.setSuccess(true);
        data.setMsg("发放成功");
        return data;
    }

    @Override
    public ResponseEntity<byte[]> downLoadPrintWayBills(List<String> waybillNoList) throws Exception {
        Assert.notEmpty(waybillNoList, "waybillNoList不能为空");
        ArrayList<Path> filePaths = new ArrayList<>();
        for (String waybillNo : waybillNoList) {
            String fileName = waybillNo + SFContestant.expressFileNameSuffix;
            Path pdfPath = Paths.get(pdfDownLoadDirectory + fileName);
            filePaths.add(pdfPath);
        }
        byte[] zipBytes = compressFilesToZip(filePaths);

        // 设置HTTP响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", URLEncoder.encode("SF面单.zip", "UTF-8"));

        // 返回ZIP文件的字节数组作为ResponseEntity
        return new ResponseEntity<>(zipBytes, headers, HttpStatus.OK);
    }

    @Override
    public Result PushOrderState(PushOrderState pushOrderState, String code) {
        List<orderState> orderStateList = pushOrderState.getOrderState();
        ArrayList<CompletableFuture<Boolean>> futures = new ArrayList<>();
        Result result = new Result(0, "", null, "true");
        log.info("请求报文:{}", pushOrderState);
        for (orderState orderState : orderStateList) {
            CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
                try {
                    return retryTemplate.execute(context -> {
                        Boolean isSuccess = posPostOrderMapper.updateWaybillNoInfo(orderState.getWaybillNo(), orderState.getOrderStateCode(), orderState.getOrderNo());
                        if (!isSuccess) {
                            throw new RuntimeException();
                        }
                        return isSuccess;
                    });
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }, threadPoolTaskExecutor).exceptionally(ex -> {
                result.setMsg("订单状态接收异常");
                result.setSuccess("false");
                log.error("Max retry count reached. Discarding result for: {}", orderState.getWaybillNo());
                return null;
            });
            futures.add(future);
        }
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        return result;

    }

    public ResponseData doPost(Object requestParam, String serviceCode) throws Exception {
        Map<String, String> params = new HashMap<>();
        String timeStamp = String.valueOf(System.currentTimeMillis());
        String msgData = objectMapper.writeValueAsString(requestParam);
        params.put(SFContestant.partnerID, clientCode);
        params.put(SFContestant.requestID, UUID.randomUUID().toString().replace("-", ""));
        params.put(SFContestant.serviceCode, serviceCode);
        params.put(SFContestant.timestamp, String.valueOf(System.currentTimeMillis()));
        params.put(SFContestant.msgData, msgData);
        params.put(SFContestant.msgDigest, CallExpressServiceTools.getMsgDigest(msgData, timeStamp, checkWord));
        long startTime = System.currentTimeMillis();
        log.info("调用实际请求:" + params);
        String doPostResult = HttpClientUtil.post(callUrl, params);
        log.info("调用丰桥的接口服务代码：" + serviceCode + " 接口耗时：" + (System.currentTimeMillis() - startTime));
        log.info("调用地址:" + callUrl);
        log.info("返回结果:" + doPostResult);
        ResponseData responseData = objectMapper.readValue(doPostResult, ResponseData.class);
        if (ObjectUtil.notEqual(responseData.getApiResultCode(), SFContestant.successApiResultCode)) {
            throw new RuntimeException(responseData.getApiErrorMsg());
        }
        if (ObjectUtil.equals(responseData.getApiResultCode(), SFContestant.successApiResultCode) && ObjectUtil.notEqual(responseData.getApiResultData().getSuccess(), true)) {
            throw new RuntimeException(responseData.getApiResultData().getErrorMsg());
        }
        return responseData;

    }

    public String doPostPrintWayBills(Object requestParam, String serviceCode) throws IOException {
        Map<String, String> params = new HashMap<>();
        String timeStamp = String.valueOf(System.currentTimeMillis());
        String msgData = objectMapper.writeValueAsString(requestParam);
        params.put(SFContestant.partnerID, clientCode);
        params.put(SFContestant.requestID, UUID.randomUUID().toString().replace("-", ""));
        params.put(SFContestant.serviceCode, serviceCode);
        params.put(SFContestant.timestamp, String.valueOf(System.currentTimeMillis()));
        params.put(SFContestant.msgData, msgData);
        params.put(SFContestant.msgDigest, CallExpressServiceTools.getMsgDigest(msgData, timeStamp, checkWord));
        long startTime = System.currentTimeMillis();
        log.info("调用实际请求:" + params);
        String result = HttpClientUtil.post(callPrintUrl, params);
        log.info("调用实际结果:" + result);
        log.info("调用丰桥的接口服务代码：" + serviceCode + " 接口耗时：" + (System.currentTimeMillis() - startTime));
        log.info("调用地址:" + callUrl);
        log.info("返回结果:" + result);
        getPrintToPdf(result);
        return result;
    }

    public String doPostPrintWayBillsForSync(Object requestParam, String serviceCode) throws IOException {
        Map<String, String> params = new HashMap<>();
        String timeStamp = String.valueOf(System.currentTimeMillis());
        String msgData = objectMapper.writeValueAsString(requestParam);
        params.put(SFContestant.partnerID, clientCode);
        params.put(SFContestant.requestID, UUID.randomUUID().toString().replace("-", ""));
        params.put(SFContestant.serviceCode, serviceCode);
        params.put(SFContestant.timestamp, String.valueOf(System.currentTimeMillis()));
        params.put(SFContestant.msgData, msgData);
        params.put(SFContestant.logisticID, SFContestant.logisticIDCode);
        params.put(SFContestant.msgDigest, CallExpressServiceTools.getMsgDigest(msgData, timeStamp, checkWord));
        long startTime = System.currentTimeMillis();
        log.info("调用实际请求:" + params);
        String result = HttpClientUtil.post(callPrintUrl, params);
        log.info("调用实际结果:" + result);
        log.info("调用丰桥的接口服务代码：" + serviceCode + " 接口耗时：" + (System.currentTimeMillis() - startTime));
        log.info("调用地址:" + callUrl);
        log.info("返回结果:" + result);
        return result;
    }

    public void getPrintToPdf(String result) throws IOException {
        ResponseData responseData = objectMapper.readValue(result, ResponseData.class);
        List<PrintFile> files = responseData.getApiResultData().getObj().getFiles();
        for (PrintFile file : files) {
            String token = file.getToken();
            String url = file.getUrl();
            ResponseEntity<byte[]> responseEntity = downLoadPdf(result, token, url);
            byte[] data = responseEntity.getBody();
            writeToPdf(data, file);
        }
    }

    public ResponseEntity<byte[]> downLoadPdf(String result, String token, String url) {
        HttpHeaders headers = new HttpHeaders();
        headers.set(SFContestant.headers, token);
        // 构建HttpEntity
        HttpEntity<String> requestEntity = new HttpEntity<>(result, headers);
        // 发送POST请求
        return restTemplate.exchange(url, HttpMethod.GET, requestEntity, byte[].class);
    }

    public void writeToPdf(byte[] data, PrintFile file) throws IOException {
        Path directory = Paths.get(pdfDownLoadDirectory);
        if (!Files.exists(directory)) {
            Files.createDirectories(directory);
        }
        String fileName = file.getWaybillNo() + SFContestant.expressFileNameSuffix;
        String filePath = Paths.get(String.valueOf(directory), fileName).toString();
        FileOutputStream fos = new FileOutputStream(filePath, false);
        fos.write(data);
        fos.flush();
        fos.close();
    }

    /**
     * 压缩文件并返回ZIP字节数组
     */
    private byte[] compressFilesToZip(List<Path> filePaths) throws Exception {
        ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
        ZipOutputStream zipOut = new ZipOutputStream(byteArray);
        for (Path filePath : filePaths) {
            String fileName = filePath.getFileName().toString();
            ZipEntry zipEntry = new ZipEntry(fileName);
            zipOut.putNextEntry(zipEntry);
            byte[] fileBytes = Files.readAllBytes(filePath);
            zipOut.write(fileBytes);
        }
        zipOut.close();
        return byteArray.toByteArray();
    }

    private <T> List<ResponseData> executeAsyncTasks(List<T> requestDataList, String serviceCode) {
        List<ResponseData> responseDataList = new CopyOnWriteArrayList<>();
        List<CompletableFuture<Void>> futures = new ArrayList<>();

        for (T requestData : requestDataList) {
            CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
                try {
                    return retryTemplate.execute(context -> {
                        log.info("Async task running in thread: {}", Thread.currentThread().getName());
                        return doPost(requestData, serviceCode);
                    });
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }, threadPoolTaskExecutor).thenAccept(responseDataList::add).exceptionally(ex -> {
                log.error("Max retry count reached. Discarding result for: {}", requestData);
                return null;
            });
            futures.add(future);
        }

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        return responseDataList;
    }
}
